Skip to content

Add AI browser prompt api#33

Open
ScriptSmith wants to merge 3 commits intomainfrom
prompt-api
Open

Add AI browser prompt api#33
ScriptSmith wants to merge 3 commits intomainfrom
prompt-api

Conversation

@ScriptSmith
Copy link
Copy Markdown
Owner

No description provided.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR adds a Browser AI integration that routes requests to Chromium's on-device LanguageModel Prompt API through a postMessage bridge, since the API is not exposed in service workers. It includes UI for availability detection and model download, a service-worker intercept layer for /v1/models, /v1/responses, and /v1/chat/completions, and a tool-use polyfill via system-prompt injection + responseConstraint.

  • P1 — chatMessagesToBridge drops role:\"tool\" messages: Multi-turn tool-use via the chat completions path is broken. After the model emits tool calls and the client sends results back as role:\"tool\" messages, those messages are silently filtered out, leaving the model without tool-result context for its follow-up turn.

Confidence Score: 4/5

Safe to merge with the tool-result message fix applied; the rest of the integration is well-structured.

One P1 bug in chatMessagesToBridge breaks multi-turn tool-use via chat completions by silently dropping role:"tool" messages. All other paths (Responses API, streaming, availability caching, bridge lifecycle) look correct.

ui/src/service-worker/browser-ai.ts — chatMessagesToBridge function needs role:"tool" handling before this is used in production tool-use flows.

Important Files Changed

Filename Overview
ui/src/service-worker/browser-ai.ts New 1,349-line SW module implementing Browser AI routing; chatMessagesToBridge silently drops role:"tool" messages, breaking multi-turn tool-use via the chat completions path.
ui/src/components/WasmSetup/WasmSetupGuard.tsx Adds browser AI state management, download flow, and cache-invalidation message to the SW; error path hard-codes "downloadable" regardless of actual post-failure availability.
ui/src/services/browser-ai/bridge.ts Window-side bridge relaying LanguageModel calls from the SW; cumulative-vs-delta detection and abort/session-destroy handling look correct.
ui/src/services/browser-ai/availability.ts Clean UA-based model detection and availability helpers; no issues found.
ui/src/components/WasmSetup/BrowserAiCard.tsx New UI card for browser AI status and download trigger; progress clamping and state derivation are correct.
ui/src/service-worker/sw.ts Wires browser AI intercept into the fetch handler; request.clone() correctly preserves body for non-browser-AI requests.
ui/src/services/browser-ai/types.ts Type declarations for the Prompt API; well-documented with spec references.
ui/src/components/WasmSetup/WasmSetup.tsx Refactors BrowserAiProp into a bundled type to prevent silent no-op callbacks; no issues found.

Sequence Diagram

sequenceDiagram
    participant UI as WasmSetupGuard (Window)
    participant Bridge as BrowserAiBridge (Window)
    participant SW as Service Worker
    participant LM as LanguageModel API

    UI->>Bridge: installBrowserAiBridge()
    UI->>LM: getAvailability()
    LM-->>UI: downloadable | available | ...

    Note over UI: User clicks Download
    UI->>LM: lm.create({ monitor })
    LM-->>UI: downloadprogress events (loaded in [0,1])
    UI->>UI: setBrowserAi({ downloadProgress })
    LM-->>UI: session (download complete)
    UI->>SW: postMessage(BROWSER_AI_AVAILABILITY_CHANGED)
    SW->>SW: invalidateAvailabilityCache()
    UI->>UI: queryClient.invalidateQueries(/v1/models)

    Note over SW: Fetch /v1/models
    SW->>SW: augmentModelsResponse()
    SW->>Bridge: postMessage(BROWSER_AI_REQUEST AVAILABILITY)
    Bridge->>LM: getAvailability()
    LM-->>Bridge: available
    Bridge-->>SW: type AVAILABILITY state available
    SW-->>UI: models list + browser/gemini-nano entry

    Note over SW: Chat request to browser/* model
    SW->>Bridge: postMessage(BROWSER_AI_REQUEST PROMPT)
    Bridge->>LM: lm.create() + promptStreaming()
    LM-->>Bridge: DELTA chunks
    Bridge-->>SW: type DELTA text
    LM-->>Bridge: done
    Bridge-->>SW: type DONE inputTokens outputTokens
    SW-->>UI: SSE stream / JSON response
Loading
Prompt To Fix All With AI
This is a comment left during a code review.
Path: ui/src/service-worker/browser-ai.ts
Line: 931-942

Comment:
**`role: "tool"` messages silently dropped in multi-turn tool use**

`chatMessagesToBridge` filters out any message whose role is not `"system"`, `"user"`, or `"assistant"`. In the OpenAI chat completions format, tool results are sent back by the caller as messages with `role: "tool"`. These are silently discarded, so after the model emits a tool call and the client sends the result, the follow-up prompt to `generateChatCompletionsToolModeResponse` contains no tool-result context — the model sees only the user's original message and will either repeat the tool call or produce a confused response.

The bridge's `LanguageModelMessage` only supports `"system" | "user" | "assistant"`, so tool-result turns need to be re-serialized as `"user"` messages with the same `<tool_result name="...">` markup that `inputToMessages` already emits for the Responses-API path.

How can I resolve this? If you propose a fix, please make it concise.

---

This is a comment left during a code review.
Path: ui/src/components/WasmSetup/WasmSetupGuard.tsx
Line: 409-416

Comment:
**Error catch always resets availability to `"downloadable"`**

On any download failure the catch block hard-codes `availability: "downloadable"`, but that's not necessarily the actual state. If the download fails because the device became ineligible mid-download (e.g. storage pressure), Chrome may now report `"unavailable"`. Setting `"downloadable"` in that case causes the Download button to reappear and immediately fail again on every attempt. Re-querying the real availability on failure and only falling back to `"downloadable"` if the query itself throws would avoid the stale state.

How can I resolve this? If you propose a fix, please make it concise.

Reviews (2): Last reviewed commit: "Review fixes" | Re-trigger Greptile

Comment thread ui/src/service-worker/browser-ai.ts
@ScriptSmith
Copy link
Copy Markdown
Owner Author

@greptile-apps

Comment on lines +931 to +942
delta: reply.text,
});
return false;
}
if (reply.type === "DONE") {
inputTokens = reply.inputTokens;
outputTokens = reply.outputTokens;
return true;
}
if (reply.type === "ERROR") {
throw new Error(reply.message);
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 role: "tool" messages silently dropped in multi-turn tool use

chatMessagesToBridge filters out any message whose role is not "system", "user", or "assistant". In the OpenAI chat completions format, tool results are sent back by the caller as messages with role: "tool". These are silently discarded, so after the model emits a tool call and the client sends the result, the follow-up prompt to generateChatCompletionsToolModeResponse contains no tool-result context — the model sees only the user's original message and will either repeat the tool call or produce a confused response.

The bridge's LanguageModelMessage only supports "system" | "user" | "assistant", so tool-result turns need to be re-serialized as "user" messages with the same <tool_result name="..."> markup that inputToMessages already emits for the Responses-API path.

Prompt To Fix With AI
This is a comment left during a code review.
Path: ui/src/service-worker/browser-ai.ts
Line: 931-942

Comment:
**`role: "tool"` messages silently dropped in multi-turn tool use**

`chatMessagesToBridge` filters out any message whose role is not `"system"`, `"user"`, or `"assistant"`. In the OpenAI chat completions format, tool results are sent back by the caller as messages with `role: "tool"`. These are silently discarded, so after the model emits a tool call and the client sends the result, the follow-up prompt to `generateChatCompletionsToolModeResponse` contains no tool-result context — the model sees only the user's original message and will either repeat the tool call or produce a confused response.

The bridge's `LanguageModelMessage` only supports `"system" | "user" | "assistant"`, so tool-result turns need to be re-serialized as `"user"` messages with the same `<tool_result name="...">` markup that `inputToMessages` already emits for the Responses-API path.

How can I resolve this? If you propose a fix, please make it concise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant